#ifndef FRINK_PROPNET_H
#define FRINK_PROPNET_H

#include "parser.h"

#include <vector>
#include <string>
#include <unordered_map>
#include <map>
#include <set>

namespace PropNet {

enum class CompType {
    base,
    input,
    goal,
    terminal,
    view,
    conjunc, // and-gate
    disjunc, // or-gate
    negation,
    transition,
    constant
};

class Component {
  public:
    Component(std::string name_, CompType type_, bool mark_ = false) : name{ name_ }, type{ type_ }, sources{}, mark{ mark_ }
    {
    }

    std::string typeString() const;

    std::string name;
    CompType type;
    std::vector<Component *> sources;
    bool mark;
};

class PropNet {
  public:
    PropNet();

    size_t getRole(std::string rolename) const
    {
        return roles.at(rolename); // with error checking
    }

    /** builds propnet from tokens [first, last)
     *   return true on success */
    bool loadGDL(std::vector<Parser::Token>::const_iterator first, std::vector<Parser::Token>::const_iterator last);
    /** builds propnet from token vector */
    bool loadGDL(std::vector<Parser::Token> tokens)
    {
        return loadGDL(tokens.begin(), tokens.end());
    }

    /** if it errors it simply returns with no error handling
     *  overwrites contents of filename */
    void writeDOT(std::string const &filename) const;


    typedef std::map<std::string, std::set<std::string>> OptionContainer;
    typedef Parser::VariableReplacements VariableReplacements;

  protected:
    std::vector<std::unique_ptr<Component>> components; // contains all components, the other vectors point to its content
    std::unordered_map<std::string, Component*> bases;
    std::unordered_map<std::string, Component*> views;  // propositions that are not baseprops
    std::vector<std::unordered_map<std::string, Component *> > inputs;
    std::vector<std::unordered_map<std::string, Component *> > legals; // legal props are not base props! (they are
                                                                       // simply calculated from the propnet)
    std::vector<std::map<int, Component *> > goals;
    std::vector<Component*> initials; // base props that are true in initial state
    Component terminal;
    Component comp_true; // constant component that's always true  (used for e.g 'distinct')
    Component comp_false; // constant component that's always false  (used for e.g 'distinct')
    std::map<std::string, size_t> roles; // link role name to role number that indexes into other vectors

  private:
    Component *addAndGate();
    Component *addOrGate();
    Component *addNotGate();
    Component *addGoal(std::string const &rolename, std::string const &utilitystr);
    void defineKeywordGDL(Parser::GdlKeywordProposition const *kwd, VariableReplacements const &var_replacements);
    void definePropositionGDL(Parser::GdlProposition *prop);
    Component *getKeywordComponent(Parser::GdlKeywordProposition const *gdlterm,
                                   VariableReplacements const &variable_replacements);
    Component *getComponent(Parser::GdlTerm const *gdlterm, VariableReplacements const &var_replacements);

    bool evaluateGDLTrue(Parser::GdlTerm const *term, VariableReplacements const &variable_replacements) const;
    void defineAssignmentGDL(Parser::GdlAssignment const *ass, VariableReplacements const &variable_replacement_map);
    void loadVariableSentence(Parser::GdlSentence const &sentence,
                              std::map<std::string, std::vector<Parser::GdlTerm const *>> varmap);
    bool propExists(std::string const &str) const;
    bool propExists(Parser::GdlProposition const *gdlprop, std::map<std::string, std::string> const &map) const;
    bool propExists(Parser::GdlKeywordProposition const *gdlkwdprop, std::map<std::string, std::string> const &map) const;
    void findOptions(Parser::GdlProposition const *gdlprop, OptionContainer &options) const;
    void findOptions(Parser::GdlKeywordProposition const *gdlkwd, PropNet::OptionContainer &options) const;


    PropNet &operator=(PropNet const &) = delete;
};

} // namespace PropNet

#endif // FRINK_PROPNET_H